﻿using FileTransfer.Except;
using FileTransfer.Model;
using HAF.LogUtility;
using HAF.LogUtility.Model;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Threading;

namespace FileTransfer.Assistantor
{
    /// <summary>
    /// FTP助手
    /// </summary>
    public class FtpAssistantor : IDisposable
    {
        #region 外部扩展事件
        /// <summary>
        /// 文件上传消息输出
        /// </summary>
        public event Action<double> UploadProgressChangeEvent;
        /// <summary>
        /// 文件下载消息输出
        /// </summary>
        public Action<String> DownloadMessageEvent;
        /// <summary>
        /// 文件下载进度输出
        /// </summary>
        public Action<int> DownloadProcessChangeEvent;
        /// <summary>
        /// 文件下载状态输出
        /// </summary>
        public Action<int, long, long, long, long> DownloadChangeEvent;
        #endregion 外部扩展事件

        bool stopFlag = false;

        Guid currentFileId = Guid.Empty;
        /// <summary>  
        /// FTP请求对象  
        /// </summary>  
        FtpWebRequest request = null;
        /// <summary>  
        /// FTP响应对象
        /// </summary>  
        FtpWebResponse response = null;
        /// <summary>  
        /// FTP服务器地址  
        /// </summary>  
        public string FtpURI { get; private set; }
        /// <summary>  
        /// FTP服务器IP  
        /// </summary>  
        public string FtpServerIP { get; private set; }
        /// <summary>  
        /// FTP服务器默认目录  
        /// </summary>  
        public string FtpRemotePath { get; private set; }
        /// <summary>  
        /// FTP服务器登录用户名  
        /// </summary>  
        public string FtpUserID { get; private set; }
        /// <summary>  
        /// FTP服务器登录密码  
        /// </summary>  
        public string FtpPassword { get; private set; }

        //缓存默认100KB
        int _cacheSize = 1024 * 100;
        /// <summary>
        /// 下载流缓存
        /// </summary>
        public int CacheSize
        {
            get { return _cacheSize; }
            set { _cacheSize = value; }
        }

        static Dictionary<string, object> dicLockObjct = new Dictionary<string, object>();

        /// <summary>    
        /// 初始化  
        /// </summary>    
        /// <param name="ftpServerIP">FTP连接地址</param>
        /// <param name="ftpPort"></param>
        /// <param name="ftpRemotePath">指定FTP连接成功后的当前目录, 如果不指定即默认为根目录</param>    
        /// <param name="ftpUserID">用户名</param>    
        /// <param name="ftpPassword">密码</param>    
        public FtpAssistantor(Guid fileId, string ftpServerIP, int ftpPort, string ftpRemotePath, string ftpUserID, string ftpPassword)
        {
            this.currentFileId = fileId;
            this.FtpServerIP = ftpServerIP;
            this.FtpRemotePath = ftpRemotePath;
            this.FtpUserID = ftpUserID;
            this.FtpPassword = ftpPassword;
            if (string.IsNullOrEmpty(ftpRemotePath))
            {
                this.FtpURI = "ftp://" + ftpServerIP + ":" + ftpPort + "/";
            }
            else
            {
                this.FtpURI = "ftp://" + ftpServerIP + ":" + ftpPort + "/" + ftpRemotePath + "/";
            }


            //TaskEventManager.StopTaskEvent += TaskEventManager_StopTaskEvent;
        }

        /// <summary>
        /// 
        /// </summary>
        ~FtpAssistantor()
        {
            try
            {
                if (response != null)
                {
                    response.Close();
                    response = null;
                }
                if (request != null)
                {
                    request.Abort();
                    request = null;
                }
            }
            catch
            { }

            //TaskEventManager.StopTaskEvent -= TaskEventManager_StopTaskEvent;
        }
        /// <summary>  
        /// 建立FTP链接,返回响应对象  
        /// </summary>  
        /// <param name="uri">FTP地址</param>  
        /// <param name="ftpMethod">操作命令</param>  
        /// <returns></returns>  
        private FtpWebResponse Open(Uri uri, string ftpMethod, string group = "")
        {
            try
            {
                WriteDebugLog(string.Format("执行指令为：ftp {0} {1}", uri.ToString(), ftpMethod));
                FtpWebRequest request = (FtpWebRequest)FtpWebRequest.Create(uri);
                request.Method = ftpMethod;
                request.UseBinary = true;  //使用二进制
                request.KeepAlive = true;
                if (!string.IsNullOrEmpty(group))
                {
                    request.ConnectionGroupName = group;
                }
                //设置最大链接数 默认2
                request.ServicePoint.ConnectionLimit = 4;
                //request1.UsePassive = true;  //使用主动或者被动模式 将UsePassive属性设置为true会将“PASV”命令发送到服务器
                request.Credentials = new NetworkCredential(this.FtpUserID, this.FtpPassword);
                //设置30分钟的超时时间 30 * 60 * 1000; 4K视频太大，下载时间很长
                request.Timeout = 1800000;

                return (FtpWebResponse)request.GetResponse();
            }
            catch (Exception ex)
            {
                WriteErrorLog(ex.ToString());
                return null;
            }
            
        }

        /// <summary>         
        /// 建立FTP链接,返回请求对象         
        /// </summary>        
        /// <param name="uri">FTP地址</param>         
        /// <param name="ftpMethod">操作命令</param>         
        private FtpWebRequest OpenRequest(Uri uri, string ftpMethod)
        {
            Debug.WriteLine("ftp {0} {1}", uri.ToString(), ftpMethod);
            var request1 = (FtpWebRequest)WebRequest.Create(uri);
            request1.Method = ftpMethod;
            request1.UseBinary = true;
            request1.KeepAlive = false;
            request1.Timeout = 1800000;
            request1.Credentials = new NetworkCredential(this.FtpUserID, this.FtpPassword);
            return request1;
        }
        /// <summary>  
        /// 创建目录  
        /// </summary>  
        /// <param name="remoteDirectoryName">目录名</param>  
        public void CreateDirectory(string remoteDirectoryName)
        {
            Open(new Uri(FtpURI + remoteDirectoryName), FtpCmd.MakeDirectory); // WebRequestMethods.Ftp.MakeDirectory
        }
        /// <summary>  
        /// 更改目录或文件名  
        /// </summary>  
        /// <param name="currentName">当前名称</param>  
        /// <param name="newName">修改后新名称</param>  
        public void ReName(string currentName, string newName)
        {
            var request1 = OpenRequest(new Uri(FtpURI + currentName), FtpCmd.Rename);
            request1.RenameTo = newName;
            request1.GetResponse();
        }

        /// <summary>
        /// 获取指定文件大小
        /// </summary>
        /// <param name="remoteFileName"></param>
        /// <returns></returns>
        public long GetFileSize(string remoteFileName)
        {
            try
            {
                var response1 = Open(new Uri(FtpURI + remoteFileName), FtpCmd.GetFileSize);
                return response1.ContentLength;
            }
            catch
            {
                return 0;
            }
        }

        /// <summary>    
        /// 切换当前目录    
        /// </summary>    
        /// <param name="isRoot">true:绝对路径 false:相对路径</param>     
        public void GotoDirectory(string directoryName, bool isRoot)
        {
            if (isRoot)
                FtpRemotePath = directoryName;
            else
                FtpRemotePath += "/" + directoryName;

            FtpURI = "ftp://" + FtpServerIP + "/" + FtpRemotePath + "/";
        }


        /// <summary>  
        /// 删除目录(包括下面所有子目录和子文件)  
        /// </summary>  
        /// <param name="remoteDirectoryName">要删除的带路径目录名：如web/test</param>  
        /* 
         * 例：删除test目录 
         FTPHelper helper = new FTPHelper("x.x.x.x", "web", "user", "password");                   
         helper.RemoveDirectory("web/test"); 
         */
        public void RemoveDirectory(string remoteDirectoryName)
        {
            GotoDirectory(remoteDirectoryName, true);
            var listAll = ListFilesAndDirectories();
            foreach (var m in listAll)
            {
                if (m.IsDirectory)
                    RemoveDirectory(m.RemotePath);
                else
                    DeleteFile(m.Name);
            }
            GotoDirectory(remoteDirectoryName, true);
            response = Open(new Uri(FtpURI), WebRequestMethods.Ftp.RemoveDirectory);
        }

        /// <summary>
        /// 文件上传
        /// </summary>
        /// <param name="fs">流 文件流或者内存流</param>
        /// <param name="targeFileName">文件名</param>
        public void Upload(Stream fs, string targeFileName)
        {
            request = OpenRequest(new Uri(FtpURI + targeFileName), WebRequestMethods.Ftp.UploadFile);
            request.ContentLength = fs.Length;
            int buffLength = 1024 * 500;//500kb
            byte[] buff = new byte[buffLength];
            int contentLen;
            long startFileSize = 0;
            try
            {

                //获取ftp上的文件大小,实现断点续传功能
                startFileSize = GetFileSize(targeFileName);

                //重新设置文件流的位置实现断点续传功能
                fs.Position = startFileSize;

                if (startFileSize == fs.Length)
                {
                    if (UploadProgressChangeEvent != null)
                        UploadProgressChangeEvent(100);
                    return;
                }

                using (var strm = request.GetRequestStream())
                {
                    contentLen = fs.Read(buff, 0, buffLength);
                    while (contentLen != 0)
                    {
                        if (stopFlag)
                        {
                            return;
                        }
                        strm.Write(buff, 0, contentLen);

                        var progress = Convert.ToDouble(fs.Position) / fs.Length * 100;
                        if (UploadProgressChangeEvent != null)
                            UploadProgressChangeEvent(progress);

                        contentLen = fs.Read(buff, 0, buffLength);
                    }
                }
            }
            catch
            {
                response = (FtpWebResponse)request.GetResponse();
                response.Close();
            }
        }

        /// <summary>
        /// 当线程任务都处理完成后 清理缓存
        /// </summary>
        public static void ClearDicLocks()
        {
            try
            {
                dicLockObjct.Clear();
            }
            catch { }
        }

        private object GetLockObject(string key)
        {
            if (!dicLockObjct.ContainsKey(key))
            {
                lock (dicLockObjct)
                {
                    if (!dicLockObjct.ContainsKey(key))
                    {
                        object lockObject = new object();
                        dicLockObjct.Add(key, lockObject);
                    }
                }
            }

            return dicLockObjct[key];
        }
        /// <summary>  
        /// 文件上传  
        /// </summary>  
        /// <param name="localFilePath">本地文件路径</param>  
        public void Upload(string localFilePath, string targetFilePath)
        {
            var lockObject = GetLockObject(localFilePath);

            lock (lockObject)
            {
                if (!File.Exists(localFilePath))
                    return;
                System.Threading.Thread.Sleep(10 * 1000);
                FileInfo fileInf = new FileInfo(localFilePath);
                using (var fsRead = new FileStream(localFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                {
                    Upload(fsRead, System.IO.Path.GetFileName(targetFilePath));
                }
            }
            //request = OpenRequest(new Uri(ftpURI + fileInf.Name), WebRequestMethods.Ftp.UploadFile);
            //request.ContentLength = fileInf.Length;
            //int buffLength = 2048;
            //byte[] buff = new byte[buffLength];
            //int contentLen;
            //using (var fs = fileInf.OpenRead())
            //{
            //    using (var strm = request.GetRequestStream())
            //    {
            //        contentLen = fs.Read(buff, 0, buffLength);
            //        while (contentLen != 0)
            //        {
            //            strm.Write(buff, 0, contentLen);
            //            contentLen = fs.Read(buff, 0, buffLength);
            //        }
            //    }
            //}
        }
        /// <summary>    
        /// 删除文件    
        /// </summary>    
        /// <param name="remoteFileName">要删除的文件名</param>  
        public void DeleteFile(string remoteFileName)
        {
            Open(new Uri(FtpURI + remoteFileName), WebRequestMethods.Ftp.DeleteFile);
        }

        /// <summary>  
        /// 获取当前目录的文件和一级子目录信息  
        /// </summary>  
        /// <returns></returns>  
        public List<FileStruct> ListFilesAndDirectories()
        {
            var fileList = new List<FileStruct>();
            var response1 = Open(new Uri(FtpURI), WebRequestMethods.Ftp.ListDirectoryDetails);
            using (var stream = response1.GetResponseStream())
            {
                using (var sr = new StreamReader(stream))
                {
                    string line = null;
                    while ((line = sr.ReadLine()) != null)
                    {
                        //line的格式如下：  
                        //08-18-13  11:05PM       <DIR>          aspnet_client  
                        //09-22-13  11:39PM                 2946 Default.aspx  
                        //todoline格式不对 drwx------ 1 user group              0 Aug 30 07:22 DOTA2Setup20160720
                        //-rwxr-xr-x    1 ftp      ftp       2542105 Feb 27 09:37 07c03a2f-2775-44a4-a549-8e6d06a30889_MOBILE_SD_640X480_350_CBR.mp4
                        //NLogManager.Logger.Debug("ftp文件格式 {0}", line);

                        //DateTime dtDate = DateTime.ParseExact(line.Substring(0, 8), "MM-dd-yy", null);
                        //DateTime dtDateTime = DateTime.Parse(dtDate.ToString("yyyy-MM-dd") + line.Substring(8, 9));
                        string[] arrs = line.Split(new string[] { "  " }, StringSplitOptions.RemoveEmptyEntries);
                        var model = new FileStruct()
                        {
                            IsDirectory = line.IndexOf("<DIR>") > 0 ? true : false,
                            CreateTime = DateTime.Now.ToString(),
                            Name = arrs[arrs.Length - 1],
                            RemotePath = FtpRemotePath + "/" + arrs[arrs.Length - 1]
                        };
                        fileList.Add(model);
                    }
                }
            }
            return fileList;
        }
        /// <summary>         
        /// 列出当前目录的所有文件         
        /// </summary>         
        public List<FileStruct> ListFiles()
        {
            var listAll = ListFilesAndDirectories();
            var listFile = listAll.Where(m => m.IsDirectory == false).ToList();
            return listFile;
        }

        /// <summary>
        /// 列出当前目录的所有文件
        /// </summary>
        /// <returns></returns>
        public List<string> GetFiles()
        {
            var fileList = new List<string>();
            var response1 = Open(new Uri(FtpURI), WebRequestMethods.Ftp.ListDirectoryDetails);
            using (var stream = response1.GetResponseStream())
            {
                using (var sr = new StreamReader(stream))
                {
                    string line = null;
                    while ((line = sr.ReadLine()) != null)
                    {
                        //line的格式如下：  
                        fileList.Add(line.Trim());
                    }
                }
            }
            return fileList;
        }

        /// <summary>         
        /// 列出当前目录的所有一级子目录         
        /// </summary>         
        public List<FileStruct> ListDirectories()
        {
            var listAll = ListFilesAndDirectories();
            var listFile = listAll.Where(m => m.IsDirectory == true).ToList();
            return listFile;
        }
        /// <summary>         
        /// 判断当前目录下指定的子目录或文件是否存在         
        /// </summary>         
        /// <param name="remoteName">指定的目录或文件名</param>        
        public bool IsExist(string remoteName)
        {
            var list = ListFilesAndDirectories();
            if (list.Count(m => m.Name == remoteName) > 0)
                return true;
            return false;
        }
        /// <summary>         
        /// 判断当前目录下指定的一级子目录是否存在         
        /// </summary>         
        /// <param name="remoteDirectoryName">指定的目录名</param>        
        public bool IsDirectoryExist(string remoteDirectoryName)
        {
            var listDir = ListDirectories();
            if (listDir.Count(m => m.Name == remoteDirectoryName) > 0)
                return true;
            return false;
        }
        /// <summary>         
        /// 判断当前目录下指定的子文件是否存在        
        /// </summary>         
        /// <param name="remoteFileName">远程文件名</param>
        public bool IsFileExist(string remoteFileName)
        {
            var listFile = ListFiles();
            if (listFile.Count(m => m.Name == remoteFileName) > 0)
                return true;
            return false;
        }

        /// <summary>
        /// 判断文件是否存在
        /// </summary>
        /// <remarks>you can just check Timestamp instead.</remarks>
        /// <param name="remoteFileName"></param>
        /// <returns></returns>
        public bool IsFileExists(string remoteFileName)
        {
            try
            {
                WriteDebugLog(string.Format("检查文件{0}是否存在", remoteFileName));
                var response1 = Open(new Uri(FtpURI + remoteFileName), WebRequestMethods.Ftp.GetDateTimestamp);
                if (response1.StatusCode == FtpStatusCode.FileStatus)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            catch (WebException ex)
            {
                ResponseLog result = new ResponseLog();
                if ((ex.Response as FtpWebResponse) != null)
                {
                    result.Code = (int)(ex.Response as FtpWebResponse).StatusCode;
                    result.Message = (ex.Response as FtpWebResponse).StatusDescription;
                }
                else
                {
                    result.Code = (int)ex.Status;
                    result.Message = ex.Message;
                }
                WriteErrorLog(string.Format("[{0}]文件{1}获取失败：{2}", result.Code, remoteFileName, result.Message));
                return false;
            }
        }

        #region 输出日志
        private void WriteInfoLog(string msg)
        {
            if (DownloadMessageEvent != null)
            {
                DownloadMessageEvent("[INFO] " + msg);
            }
            else
            {
                LogHelper.WriteInfoLogEx("FtpManager", msg);
            }
            //FileDownloadProgressChangeEvent?.Invoke(msg);
        }

        private void WriteDebugLog(string msg)
        {
            if (DownloadMessageEvent != null)
            {
                DownloadMessageEvent("[DEBUG] " + msg);
            }
            else
            {
                LogHelper.WriteErrorLogEx("FtpManager", msg);
            }
        }

        private void WriteErrorLog(string msg)
        {
            if (DownloadMessageEvent != null)
            {
                DownloadMessageEvent("[ERROR] " + msg);
            }
            else
            {
                LogHelper.WriteErrorLogEx("FtpManager", msg);
            }
        }
        
        /// <summary>
        /// 更新进度条
        /// </summary>
        /// <param name="progress"></param>
        private void UpdateProcess(int progress)
        {
            DownloadProcessChangeEvent?.Invoke(progress);
        }

        /// <summary>
        /// 更新进度（处理了多少字节）
        /// </summary>
        /// <param name="id">线程序号</param>
        /// <param name="size">文件大小</param>
        /// <param name="progress">读取进度</param>
        /// <param name="position">写入进度</param>
        private void ReportProcess(int id, long size, long progress, long position, long flowsize)
        {
            DownloadChangeEvent?.Invoke(id, size, progress, position, flowsize);
        }
        #endregion 输出日志

        /// <summary>  
        /// 下载资源
        /// </summary>  
        /// <remarks>不进行MD5计算</remarks>
        /// <param name="saveFileName">下载后保存地址</param>  
        /// <param name="remoteFileName">待下载文件地址</param>  
        public ResponseLog Download(string saveFileName, string remoteFileName)
        {
            ResponseLog result = new ResponseLog() { Code = (int)TransferCode.Success, Message = "成功" };
            //最小值默认100MB，最大值默认1MB
            FtpThreadStatu runThread = new FtpThreadStatu() { ThreadId = "0", MaxRate = 1 * 1024 * 1024, MinRate = 100 * 1024 * 1024 };

            try
            {
                saveFileName = saveFileName.Replace("/", "\\");
                //本地文件存储目录
                string saveDir = saveFileName.Substring(0, saveFileName.LastIndexOf(@"\"));
                if (!Directory.Exists(saveDir))
                {
                    Directory.CreateDirectory(saveDir);
                    WriteInfoLog(String.Format("创建下载目录{0}", saveDir));
                }
                Stopwatch _stopwathc = new Stopwatch();
                _stopwathc.Start();

                //var responseContext = Open(new Uri(FtpURI + "/typecmd.xml"), WebRequestMethods.Ftp.DownloadFile);
                //if (responseContext != null)
                //{
                //    WriteInfoLog(String.Format("返回：{0}  {1}", responseContext.StatusCode, responseContext.StatusDescription));
                //}

                //第一步：通过获取文件大小，判断文件是否存在
                FtpWebRequest reqCmd = (FtpWebRequest)FtpWebRequest.Create(new Uri(FtpURI + remoteFileName));
                reqCmd.Method = WebRequestMethods.Ftp.GetDateTimestamp;
                reqCmd.UseBinary = true;  //使用二进制
                reqCmd.KeepAlive = false;
                reqCmd.ConnectionGroupName = "CNTV";
                //System.Net.ServicePointManager.DefaultConnectionLimit can used to increase concurrent connections 
                reqCmd.ServicePoint.ConnectionLimit = 4;
                //request1.UsePassive = true;  //使用主动或者被动模式 将UsePassive属性设置为true会将“PASV”命令发送到服务器
                reqCmd.Credentials = new NetworkCredential(this.FtpUserID, this.FtpPassword);
                //设置30分钟的超时时间 30 * 60 * 1000; 4K视频太大，下载时间很长
                reqCmd.Timeout = 1800000;

                FtpWebResponse responseContext = (FtpWebResponse)reqCmd.GetResponse();
                WriteDebugLog(string.Format("服务器欢迎消息{0}，正在链接{1} {2}", responseContext.WelcomeMessage, responseContext.BannerMessage, reqCmd.ServicePoint.ConnectionName));
                if (responseContext.StatusCode != FtpStatusCode.FileStatus)
                {
                    return new ResponseLog() { Code = (int)TransferCode.FileNotFound, Message = string.Format("文件{0}不存在", remoteFileName) };
                }
                WriteDebugLog(string.Format("FTP指令{0}响应码为{3}{1} {2}", WebRequestMethods.Ftp.GetDateTimestamp, responseContext.StatusCode, responseContext.StatusDescription, Environment.NewLine));

                //第二步：获取文件大小
                //long dataLength = responseContext.ContentLength;

                //if (!IsFileExists(remoteFileName))
                //{
                //    return new ResponseLog() { Code = (int)EnumFTPServiceCode.FileNotFound, Message = string.Format("文件{0}不存在", remoteFileName) };
                //}

                //long dataLength = GetFileSize(remoteFileName);
                //runThread.Size = dataLength;
                //WriteInfoLog(String.Format("到FTP服务端获取介质文件{0}大小为{1}（{2}字节）", remoteFileName, GetLengthStr(dataLength), dataLength));
                //if (dataLength > 0)
                {
                    WriteDebugLog(String.Format("开始下载文件{0}到{1}", remoteFileName, saveFileName));
                    bool isFileExist = false;
                    //if (File.Exists(saveFileName))
                    //{
                    //    isFileExist = true;
                    //    FileInfo info = new FileInfo(saveFileName);
                    //    WriteDebugLog(string.Format("本地文件{0}已存在，目前大小为{1}", saveFileName, info.Length));
                    //    if (info.Length.Equals(dataLength))
                    //    {
                    //        WriteDebugLog(string.Format("经过比对，文件{0}已经正常下载完成，不用重复下载", remoteFileName));
                    //        return result;
                    //    }
                    //}
                    //创建本地文件
                    using (FileStream downStream = new FileStream(saveFileName, isFileExist ? FileMode.OpenOrCreate : FileMode.Create))
                    {
                        FlowAssistantor flow = new FlowAssistantor();
                        //System.Threading.Timer timer = new System.Threading.Timer(new System.Threading.TimerCallback(timerCall), null, 0, 1000);
                        //第二步：创建下载链路
                        //responseContext = Open(new Uri(FtpURI + remoteFileName), WebRequestMethods.Ftp.DownloadFile);
                        reqCmd = (FtpWebRequest)FtpWebRequest.Create(new Uri(FtpURI + remoteFileName));
                        reqCmd.Method = WebRequestMethods.Ftp.DownloadFile;
                        reqCmd.UseBinary = true;  //使用二进制
                        reqCmd.KeepAlive = false;
                        reqCmd.ConnectionGroupName = "CNTV";
                        reqCmd.ServicePoint.ConnectionLimit = 4;
                        //request1.UsePassive = true;  //使用主动或者被动模式 将UsePassive属性设置为true会将“PASV”命令发送到服务器
                        reqCmd.Credentials = new NetworkCredential(this.FtpUserID, this.FtpPassword);
                        //设置30分钟的超时时间 30 * 60 * 1000; 4K视频太大，下载时间很长
                        reqCmd.Timeout = 1800000;
                        
                        responseContext = (FtpWebResponse)reqCmd.GetResponse();
                        WriteDebugLog(string.Format("服务器欢迎消息{0}，正在链接{1} {2}", responseContext.WelcomeMessage, responseContext.BannerMessage, reqCmd.ServicePoint.ConnectionName));

                        WriteDebugLog(string.Format("FTP指令{0}响应码为{1} {2}", WebRequestMethods.Ftp.DownloadFile, responseContext.StatusCode, responseContext.StatusDescription));
                        using (Stream ftpStream = responseContext.GetResponseStream())
                        {
                            //responseContext.ContentLength;
                            //直接拷贝模式，无法监控进度
                            //ftpStream.CopyTo(downStream, _cacheSize);

                            //int bufferSize = 2048; //2K Byte
                            int bufferSize = _cacheSize; //1M Byte
                            WriteInfoLog(string.Format("下载文件流缓存大小为{0}， 文件大小为{1}", bufferSize, responseContext.ContentLength));
                            //每次读取长度
                            int readLength = 0;
                            //用来记录已经将多少内容写入到了文件中
                            long readCount = 0;
                            byte[] buffer = new byte[bufferSize];
                            while ((readLength = ftpStream.Read(buffer, 0, bufferSize)) > 0)
                            {
                                //downStream.Write(buffer, 0, readLength);
                                //readCount = ftpStream.Read(buffer, 0, bufferSize);
                                //不能用buffer.Length，会让文件变大
                                downStream.Write(buffer, 0, readLength);
                                //downStream.Flush();
                                //已经读取的数量累加
                                readCount += readLength;
                                flow.ReadSize = readCount;
                                runThread.ReadSize = readCount;
                                runThread.WriteSize = downStream.Position;
                                //计算已经读取的数据百分比
                                //int percentage = (int)(readCount * 100 / dataLength);
                                //UpdateProcess(percentage);
                                //WriteDebugLog(string.Format("ReadSize{0}",flow.ReadSize));
                                //WriteDebugLog(flow.FlowSize.ToString());
                                //ReportProcess(0, dataLength, readCount, downStream.Position, flow.FlowSize);
                                //最大、最小速度判定
                                runThread.MaxRate = flow.FlowSize > 0 & flow.FlowSize > runThread.MaxRate ? flow.FlowSize : runThread.MaxRate;
                                runThread.MinRate = flow.FlowSize > 0 & flow.FlowSize < runThread.MinRate ? flow.FlowSize : runThread.MinRate;
                            }
                            //downStream.Flush();
                            buffer = null;
                        }

                        responseContext.Close();
                        responseContext = null;
                        reqCmd.Abort();
                        reqCmd = null;
                    }
                }

                //if (responseContext != null)
                //{
                //    responseContext.Close();
                //    responseContext = null;
                //}
                //if (reqCmd != null)
                //{
                //    reqCmd.Abort();
                //    reqCmd = null;
                //}

                _stopwathc.Stop();
                WriteInfoLog(String.Format("[{0}]文件{1}下载到{2}完成，最大下载速率为{3}，最小下载速率为{4}，共耗时{5}！", TransferCode.Success, 
                    remoteFileName, saveFileName, GetLengthStr(runThread.MaxRate), GetLengthStr(runThread.MinRate),
                    _stopwathc.Elapsed.TotalSeconds > 60 ? _stopwathc.Elapsed.TotalMinutes.ToString() + "分" : _stopwathc.Elapsed.TotalSeconds.ToString() + "秒"));
                return result;
            }
            catch (WebException ex)
            {
                if ((ex.Response as FtpWebResponse) != null)
                {
                    result.Code = (int)(ex.Response as FtpWebResponse).StatusCode;
                    result.Message = (ex.Response as FtpWebResponse).StatusDescription;
                }
                else
                {
                    result.Code = (int)ex.Status;
                    result.Message = ex.Message;
                }
                WriteErrorLog(string.Format("[{0}]文件{1}获取失败：{2}", result.Code, remoteFileName, result.Message));
                return result;
            }
        }

        /// <summary>
        /// 下载资源
        /// </summary>
        /// <remarks>需要进行MD5计算</remarks>
        /// <param name="saveFileName"></param>
        /// <param name="remoteFileName"></param>
        /// <returns></returns>
        public ResponseLog DownloadWithMD5(string saveFileName, string remoteFileName)
        {
            ResponseLog result = new ResponseLog() { Code = (int)TransferCode.Success, Message = "成功" };
            //最小值默认100MB，最大值默认1MB
            FtpThreadStatu runThread = new FtpThreadStatu() { ThreadId = "0", MaxRate = 1 * 1024 * 1024, MinRate = 100 * 1024 * 1024 };
            try
            {
                saveFileName = saveFileName.Replace("/", "\\");
                //本地文件存储目录
                string saveDir = saveFileName.Substring(0, saveFileName.LastIndexOf(@"\"));
                if (!Directory.Exists(saveDir))
                {
                    Directory.CreateDirectory(saveDir);
                    WriteInfoLog(String.Format("创建下载目录{0}", saveDir));
                }
                Stopwatch _stopwathc = new Stopwatch();
                _stopwathc.Start();
                //第一步：判断文件是否存在
                if (!IsFileExists(remoteFileName))
                {
                    return new ResponseLog() { Code = (int)TransferCode.FileNotFound, Message = string.Format("文件{0}不存在", remoteFileName) }; ;
                }
                //第二步：获取文件大小（用于进度展示）
                long dataLength = GetFileSize(remoteFileName);
                runThread.Size = dataLength;
                WriteInfoLog(String.Format("到FTP服务端获取介质文件{0}大小为{1} {2}字节", remoteFileName, GetLengthStr(dataLength), dataLength));
                if (dataLength > 0)
                {
                    WriteInfoLog(String.Format("开始下载文件{0}到{1}", FtpURI + remoteFileName, saveFileName));
                    bool isFileExist = false;
                    if (File.Exists(saveFileName))
                    {
                        isFileExist = true;
                        FileInfo info = new FileInfo(saveFileName);
                        WriteInfoLog(string.Format("本地文件{0}已存在，目前大小为{1}", saveFileName, info.Length));
                        if (info.Length.Equals(dataLength))
                        {
                            WriteInfoLog(string.Format("经过比对，文件{0}已经正常下载完成，不用重复下载", remoteFileName));
                            return result;
                        }
                    }
                    //创建本地文件
                    using (FileStream downStream = new FileStream(saveFileName, isFileExist ? FileMode.OpenOrCreate : FileMode.Create))
                    {
                        //第二步：创建下载链路
                        var response1 = Open(new Uri(FtpURI + remoteFileName), FtpCmd.DownloadFile);
                        using (Stream ftpStream = response1.GetResponseStream())
                        {
                            FlowAssistantor timer = new FlowAssistantor();
                            //long cl = response1.ContentLength;
                            //WriteFtpLog(String.Format("文件{0}大小为{1} {2}", downloadFileName, cl, dataLength));
                            //int bufferSize = 2048; //2K Byte
                            int bufferSize = _cacheSize; //1M Byte
                            WriteInfoLog(string.Format("下载文件流缓存大小为{0}", bufferSize));
                            int readCount = 0; //每次读取长度
                            int readLength = 0;
                            byte[] buffer = new byte[bufferSize];
                            var output = new byte[bufferSize];
                            MD5CryptoServiceProvider md5Provider = new MD5CryptoServiceProvider();
                            md5Provider.Initialize();
                            while ((readLength = ftpStream.Read(buffer, 0, bufferSize)) > 0)
                            {
                                //写盘
                                downStream.Write(buffer, 0, readLength);
                                //在这里可考虑进行MD5计算
                                md5Provider.TransformBlock(buffer, 0, Convert.ToInt32(readLength), output, 0);
                                downStream.Flush(); //将缓存数据写入实体文件
                                //readCount = ftpStream.Read(buffer, 0, bufferSize);
                                //已经读取的数量累加
                                readCount += readLength;
                                timer.ReadSize = readCount;
                                runThread.ReadSize = readCount;
                                runThread.WriteSize = downStream.Position;
                                //最大、最小速度判定
                                runThread.MaxRate = timer.FlowSize > 0 & timer.FlowSize > runThread.MaxRate ? timer.FlowSize : runThread.MaxRate;
                                runThread.MinRate = timer.FlowSize > 0 & timer.FlowSize < runThread.MinRate ? timer.FlowSize : runThread.MinRate;
                                //计算已经读取的数据百分比
                                int percentage = (int)(readCount * 100 / dataLength);
                                UpdateProcess(percentage);
                                ReportProcess(0, dataLength, readCount, downStream.Position, timer.FlowSize);
                            }
                            //完成最后计算，必须调用(由于上一部循环已经完成所有运算，所以调用此方法时后面的两个参数都为0)
                            md5Provider.TransformFinalBlock(buffer, 0, 0);
                            string md5Str = BitConverter.ToString(md5Provider.Hash).Replace("-", "");
                            WriteInfoLog(String.Format("文件{0}计算后的MD5校验值为{1}", remoteFileName, md5Str));
                            md5Provider.Clear();
                            downStream.Flush(); //将缓存数据写入实体文件
                            result.Sign = md5Str;
                        }
                    }
                }
                _stopwathc.Stop();
                //WriteInfoLog(String.Format("[{0}]文件{1}下载到{2}完成，共耗时{3}毫秒！", EnumFTPServiceCode.Success, FtpURI + remoteFileName, saveFileName, 
                //    _stopwathc.ElapsedMilliseconds));
                WriteInfoLog(String.Format("[{0}]文件{1}下载到{2}完成，最大下载速率为{3}/s，最小下载速率为{4}/s，共耗时{5}！", TransferCode.Success,
                    remoteFileName, saveFileName, GetLengthStr(runThread.MaxRate), GetLengthStr(runThread.MinRate),
                    _stopwathc.Elapsed.TotalSeconds > 60 ? _stopwathc.Elapsed.TotalMinutes.ToString() + "分" : _stopwathc.Elapsed.TotalSeconds.ToString() + "秒"));
                return result;
            }
            catch (WebException ex)
            {
                if ((ex.Response as FtpWebResponse) != null)
                {
                    result.Code = (int)(ex.Response as FtpWebResponse).StatusCode;
                    result.Message = (ex.Response as FtpWebResponse).StatusDescription;
                }
                else
                {
                    result.Code = (int)ex.Status;
                    result.Message = ex.Message;
                }
                WriteErrorLog(string.Format("[{0}]文件{1}获取失败：{2}", result.Code, remoteFileName, result.Message));
                return result;

                //var webResponse = ex.Response as System.Net.HttpWebResponse;
                //if (webResponse != null)
                //{
                //    //webResponse.StatusCode == System.Net.HttpStatusCode.Unauthorized;
                //    WriteInfoLog(string.Format("[{0}]文件下载失败：{1}", webResponse.StatusCode.ToString(), ex.Message));
                //    result.Code = (int)webResponse.StatusCode;
                //    result.Message = ex.Message;
                //}
                //else
                //{
                //    //ex.Status
                //    WriteInfoLog(string.Format("[{0}]文件下载失败：{1}", (ex.Response as HttpWebResponse)?.StatusCode.ToString() ?? ex.Status.ToString(), ex.Message));
                //    if ((ex.Response as HttpWebResponse) != null)
                //    {
                //        result.Code = (int)(ex.Response as HttpWebResponse).StatusCode;
                //    }
                //    else
                //    {
                //        result.Code = (int)ex.Status;
                //    }
                //    result.Message = ex.Message;
                //}
                //return result;
            }
        }

        #region MutliThreadDownload

        long readSize = 0;
        Dictionary<int, int> blockDics = new Dictionary<int, int>();

        /// <summary>  
        /// 下载资源
        /// </summary>  
        /// <remarks>多线程下载</remarks>
        /// <param name="saveFileName">下载后保存地址</param>  
        /// <param name="downloadFileName">待下载文件地址</param>  
        //[Obsolete("Not implemented", true)] //true：产生编译错误；false：产生编译警告
        public ResponseLog MutliDownload(string saveFileName, string downloadFileName, int runThreadCount)
        {
            ResponseLog result = new ResponseLog() { Code = (int)TransferCode.Success, Message = "成功" };
            try
            {
                saveFileName = saveFileName.Replace("/", "\\");
                //本地文件存储目录
                string saveDir = saveFileName.Substring(0, saveFileName.LastIndexOf(@"\"));
                WriteDebugLog(string.Format("下载目录{0}检查", saveDir));
                if (!Directory.Exists(saveDir))
                {
                    Directory.CreateDirectory(saveDir);
                    WriteDebugLog(String.Format("创建下载目录{0}", saveDir));
                }
                                
                Stopwatch _stopwathc = new Stopwatch();
                _stopwathc.Start();
                if (!IsFileExists(downloadFileName))
                {
                    return new ResponseLog() { Code = (int)TransferCode.FileNotFound, Message = string.Format("文件{0}不存在", downloadFileName) }; ;
                }
                //第一步：获取文件大小
                long resSize = GetFileSize(downloadFileName);
                WriteInfoLog(String.Format("到FTP服务端获取介质文件{0}大小为{1} {2}字节", downloadFileName, GetLengthStr(resSize), resSize));
                if (resSize > 0)
                {
                    WriteDebugLog(string.Format("复制操作启动中，开始准备磁盘空间，创建空文件{0}进行分块读写", saveFileName));
                    string destFileTmp = saveFileName + ".tmp";
                    if (File.Exists(destFileTmp))
                    {
                        WriteDebugLog(string.Format("临时文件已存在，删除临时文件{0}", destFileTmp));
                        File.Delete(destFileTmp);
                    }
                    //创建指定大小的空文件
                    using (FileStream fs = File.Create(destFileTmp))
                    {
                        fs.SetLength(resSize);
                        //MemoryStream stream = new MemoryStream();
                        //stream.SetLength(resInfo.Length);
                        //stream.WriteTo(fs);
                    }

                    //第二步：根据线程数、文件大小，确定分成几个区块进行处理
                    
                    //每个处理线程中文件分割数量 文件大小除以每次读取长度再除以线程数量
                    int blockCount = (int)Math.Floor((double)resSize / _cacheSize / runThreadCount);
                    WriteDebugLog(string.Format("当前共有{0}个线程将启动，每次读写缓存大小为{1}字节，每个线程需处理{2}个区块", runThreadCount, _cacheSize, blockCount));
                    //全局数据初始化
                    readSize = 0;
                    blockDics.Clear();
                    //多个文件流，是否可以写同一个文件的不同位置？
                    for (int i = 0; i < runThreadCount; i++)
                    {
                        blockDics.Add(i, 0);
                        int iNum = i;
                        long beginPosition = (long)i * blockCount * _cacheSize;
                        long endPosition = (i+1) == runThreadCount ? resSize : (long)(i+1) * blockCount * _cacheSize;
                        WriteDebugLog(string.Format("工作线程{0}开始区块字节为{1}结束位置为{2}，区块数量为{3}缓存大小为{4}", i, beginPosition, endPosition, blockCount, _cacheSize));
                        //根据分块规则，进行多线程读写区块数据
                        Thread runthread = new Thread(delegate () { blockThread(downloadFileName, destFileTmp, iNum, beginPosition, endPosition); }) { Name = "blockFtpThread" + iNum.ToString(), IsBackground = true, Priority = ThreadPriority.Highest };
                        runthread.Start();
                        //延迟4秒
                        Thread.Sleep(4000);
                    }

                    while (true)
                    {
                        if (!blockDics.ContainsValue(1) && !blockDics.ContainsValue(0))
                        {
                            UpdateProcess(100);
                            //还原原始文件名
                            File.Move(destFileTmp, saveFileName);
                            break;
                        }
                        //计算已经读取的数据百分比
                        int percentage = (int)(readSize * 100 / resSize);
                        if (percentage > 100)
                        {
                            WriteDebugLog("计算已大于100%");
                        }
                        else
                        {
                            UpdateProcess(percentage);
                        }
                    }
                }
                
                _stopwathc.Stop();
                WriteInfoLog(String.Format("[{0}]文件{1}下载到{2}完成，共耗时{3}！", TransferCode.Success, downloadFileName, saveFileName,
                    _stopwathc.Elapsed.TotalSeconds > 60 ? _stopwathc.Elapsed.TotalMinutes.ToString() + "分" : _stopwathc.Elapsed.TotalSeconds.ToString() + "秒"));
                return result;
            }
            catch (Exception)
            {
                return result;
            }
            
        }

        /// <summary>
        /// 区块下载线程
        /// </summary>
        /// <param name="srcFile">下载源地址</param>
        /// <param name="destFile">本地存储文件</param>
        /// <param name="id">线程序号</param>
        /// <param name="blockCount">每个区块操作数量</param>
        private void blockThread(string srcFile, string destFile, int id, long beginPostion, long endPosition)
        {
            WriteInfoLog(string.Format("工作线程{0}开始进行文件区块数据的读写", id));
            //最小值默认100MB，最大值默认1MB
            FtpThreadStatu runThread = new FtpThreadStatu() { ThreadId = id.ToString(), MaxRate = 1 * 1024 * 1024, MinRate = 100 * 1024 * 1024 };
            try
            {
                Stopwatch _runTime = Stopwatch.StartNew();
                _runTime.Start();

                //第一步：创建FTP链路
                var request1 = (FtpWebRequest)WebRequest.Create(new Uri(FtpURI + srcFile));
                //第二步：设置读取开始位置
                //request1.Method = string.Format("{0} {1}", FtpCmd.RestAddress, position);
                request1.Method = WebRequestMethods.Ftp.DownloadFile;
                //指定数据读取位置
                request1.ContentOffset = beginPostion;
                request1.UseBinary = true;  //使用二进制
                request1.KeepAlive = true;
                //request1.UsePassive = true;  //使用主动或者被动模式 将UsePassive属性设置为true会将“PASV”命令发送到服务器
                request1.Credentials = new NetworkCredential(this.FtpUserID, this.FtpPassword);
                //设置30分钟的超时时间 30 * 60 * 1000; 4K视频太大，下载时间很长
                request1.Timeout = 1800000;
                //第三步：启动下载
                var response = (FtpWebResponse)request1.GetResponse();
                using (Stream ftpStream = response.GetResponseStream())
                {
                    int bufferSize = _cacheSize;
                    int readCount = 0; //每次读取长度
                    long readLength = 0;
                    byte[] buffer = new byte[bufferSize];
                    using (FileStream fsWrite = new FileStream(destFile, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite))
                    {
                        FlowAssistantor timer = new FlowAssistantor();
                        //指定数据写入位置
                        //fsWrite.Seek(beginPostion, id == 0 ? SeekOrigin.Begin : SeekOrigin.Current);
                        fsWrite.Seek(beginPostion, SeekOrigin.Begin);
                        WriteDebugLog(string.Format("工作线程{0}写入开始位置为{1}，共需写入{2}字节", id, fsWrite.Position, endPosition - beginPostion));
                        while ((readCount = ftpStream.Read(buffer, 0, bufferSize)) > 0)
                        {
                            //全局进度递增
                            readSize += readCount;
                            //当前线程进度递增
                            readLength += readCount;
                            timer.ReadSize = readLength;
                            fsWrite.Write(buffer, 0, readCount);  //使用readCount还是使用buffer.Length?
                                                                  
                            //最大、最小速度判定
                            runThread.MaxRate = timer.FlowSize > 0 & timer.FlowSize > runThread.MaxRate ? timer.FlowSize : runThread.MaxRate;
                            runThread.MinRate = timer.FlowSize > 0 & timer.FlowSize < runThread.MinRate ? timer.FlowSize : runThread.MinRate;
                            //fsWrite.Flush();
                            ReportProcess(id, endPosition - beginPostion, readLength, fsWrite.Position, timer.FlowSize);
                            //当读取字节数大于结束字节数，则跳出读取循环
                            if (readLength > endPosition - beginPostion)
                            {
                                WriteDebugLog(string.Format("工作线程{0}已读取字节数为{1}，需读取字节数为{2}", id, readLength, endPosition - beginPostion));
                                break;
                            }
                        }
                        //fsWrite.Flush();
                        WriteDebugLog(string.Format("工作线程{0}当前位置为{1}", id, fsWrite.Position));
                    }
                    //清空缓冲区
                    buffer = null;
                }

                //设置线程结束
                if (blockDics.ContainsKey(id))
                {
                    blockDics[id] = 2;
                }
                _runTime.Stop();
                //WriteDebugLog(string.Format("文件指定区块{0}复制完成，共耗时{1}秒", id, _runTime.Elapsed.TotalSeconds));
                WriteDebugLog(string.Format("文件指定区块{0}复制完成，最大下载速率为{1}/s，最小下载速率为{2}/s， 共耗时{3}", id, GetLengthStr(runThread.MaxRate), GetLengthStr(runThread.MinRate),
                    _runTime.Elapsed.TotalSeconds > 60 ? _runTime.Elapsed.TotalMinutes.ToString() + "分" : _runTime.Elapsed.TotalSeconds.ToString() + "秒"));
            }
            catch (Exception ex)
            {
                WriteErrorLog(ex.ToString());
            }
            
        }

        #endregion MutliThreadDownload
        
        /// <summary>
        /// 获取字节长度字符
        /// </summary>
        /// <param name="resLength">字节数</param>
        /// <returns></returns>
        private string GetLengthStr(long resLength)
        {
            if (resLength >= (long)1024 * 1024 * 1024)
            {
                return string.Format("{0}GB", Math.Round((double)resLength / (1024 * 1024 * 1024), 2));
            }
            else if (resLength >= (long)1024 * 1024)
            {
                return string.Format("{0}MB", Math.Round((double)resLength / (1024 * 1024), 2));
            }
            else
            {
                return string.Format("{0}KB", Math.Round((double)resLength / (1024), 2));
            }
        }

        /// <summary>
        /// 下载文件成字符串
        /// </summary>
        /// <param name="downloadFileName"></param>
        /// <returns></returns>
        public string Download(string downloadFileName)
        {
            return Download(downloadFileName, Encoding.Default);
        }

        /// <summary>
        /// 下载文件成字符串
        /// </summary>
        /// <param name="downloadFileName"></param>
        /// <param name="encoding">编码格式</param>
        /// <returns></returns>
        public string Download(string downloadFileName, Encoding encoding)
        {
            var response1 = Open(new Uri(FtpURI + downloadFileName), WebRequestMethods.Ftp.DownloadFile);
            using (Stream ftpStream = response1.GetResponseStream())
            {
                StreamReader sr = new StreamReader(ftpStream);
                return sr.ReadToEnd();
            }
        }

        public void Dispose()
        {
            try
            {
                if (response != null)
                {
                    response.Close();
                    response = null;
                }
                if (request != null)
                {
                    request.Abort();
                    request = null;
                }
            }
            catch
            { }
        }

        #region 私有方法

        private void TaskEventManager_StopTaskEvent(List<Guid> ids)
        {
            if (ids == null || ids.Count == 0)
                return;
            if (ids.Contains(currentFileId))
                stopFlag = true;
        }

        #endregion
    }
}
